<template>
  <div
    v-if="ready"
    class="vue3-marquee"
    :class="{ vertical: vertical, horizontal: !vertical }"
    :style="getCurrentStyle"
    :key="componentKey"
  >
    <div
      class="transparent-overlay"
      ref="marqueeOverlayContainer"
      :aria-hidden="true"
    ></div>

    <div class="marquee" ref="marqueeContent">
      <slot></slot>
    </div>

    <div
      :aria-hidden="true"
      class="marquee"
      v-if="
        !animateOnOverflowOnly ||
        (animateOnOverflowOnly && !animateOnOverflowPause)
      "
    >
      <slot></slot>
    </div>

    <div
      :aria-hidden="true"
      class="marquee cloned"
      v-for="num in cloneAmount"
      :key="num"
    >
      <slot></slot>
    </div>
  </div>
</template>

<script lang="ts">
import {
  ref,
  onMounted,
  onBeforeUnmount,
  computed,
  watch,
  defineComponent,
  PropType,
} from 'vue'

export interface MarqueeProps {
  vertical: boolean
  direction: 'normal' | 'reverse'
  duration: number
  delay: number
  loop: number
  clone: boolean
  animateOnOverflowOnly: boolean
  pause: boolean
}

export default defineComponent({
  props: {
    vertical: {
      type: Boolean as PropType<MarqueeProps['vertical']>,
      default: false,
    },

    direction: {
      type: String as PropType<MarqueeProps['direction']>,
      default: 'normal',
    },

    duration: {
      type: Number as PropType<MarqueeProps['duration']>,
      default: 20,
    },

    delay: {
      type: Number as PropType<MarqueeProps['delay']>,
      default: 0,
    },

    loop: {
      type: Number as PropType<MarqueeProps['loop']>,
      default: 0,
    },

    clone: {
      type: Boolean as PropType<MarqueeProps['clone']>,
      default: false,
    },

    animateOnOverflowOnly: {
      type: Boolean as PropType<MarqueeProps['animateOnOverflowOnly']>,
      default: false,
    },
  },

  emits: [
    'onComplete',
    'onLoopComplete',
    'onPause',
    'onResume',
    'onOverflowDetected',
    'onOverflowCleared',
  ],

  setup(props, { emit }) {
    const cloneAmount = ref(0)

    const minWidth = ref('100%')
    const minHeight = ref('100%')

    const componentKey = ref(0)

    const verticalAnimationPause = ref(false)

    const animateOnOverflowPause = ref(true)

    const containerWidth = ref(0)
    const contentWidth = ref(0)

    const containerHeight = ref(0)
    const contentHeight = ref(0)

    const mouseOverMarquee = ref(false)
    const mouseDownMarquee = ref(false)

    const loopCounter = ref(0)
    const loopInterval = ref<any>(null)

    const gradientLength = ref('200px')

    const ready = ref(false)

    const marqueeContent = ref<HTMLDivElement | any>(null)
    const marqueeOverlayContainer = ref<HTMLDivElement | any>(null)

    const ForcesUpdate = async () => {
      await checkForClone()

      componentKey.value++
    }

    const checkForClone = async () => {
      if (props.vertical) {
        // pause the animation to prevent flickering
        verticalAnimationPause.value = true
      }

      setInterval(() => {
        minWidth.value = '0%'
        minHeight.value = '0%'

        if (
          marqueeContent.value !== null &&
          marqueeOverlayContainer.value !== null &&
          marqueeContent.value &&
          marqueeOverlayContainer.value
        ) {
          if (
            props.vertical &&
            'clientHeight' in marqueeContent.value &&
            'clientHeight' in marqueeOverlayContainer.value
          ) {
            contentHeight.value = marqueeContent.value.clientHeight
            containerHeight.value = marqueeOverlayContainer.value.clientHeight

            const localCloneAmount = Math.ceil(
              containerHeight.value / contentHeight.value,
            )

            cloneAmount.value = props.animateOnOverflowOnly
              ? 0
              : isFinite(localCloneAmount)
              ? localCloneAmount
              : 0

            // resume the animation
            verticalAnimationPause.value = false

            return cloneAmount.value
          } else if (
            !props.vertical &&
            'clientWidth' in marqueeContent.value &&
            'clientWidth' in marqueeOverlayContainer.value
          ) {
            contentWidth.value = marqueeContent.value.clientWidth
            containerWidth.value = marqueeOverlayContainer.value.clientWidth

            if (props.animateOnOverflowOnly && ready.value) {
              if (contentWidth.value <= containerWidth.value) {
                animateOnOverflowPause.value = true
                emit('onOverflowCleared')
              } else {
                animateOnOverflowPause.value = false
                emit('onOverflowDetected')
              }

              return 0 // don't clone if animateOnOverflowOnly is true
            }

            const localCloneAmount = Math.ceil(
              containerWidth.value / contentWidth.value,
            )

            cloneAmount.value = isFinite(localCloneAmount)
              ? localCloneAmount
              : 0

            return cloneAmount.value
          } else {
            minWidth.value = '100%'
            minHeight.value = '100%'
            return 0
          }
        } else {
          minWidth.value = '100%'
          minHeight.value = '100%'
          return 0
        }
      }, 100)
    }

    watch(contentWidth, async () => {
      if (props.clone) {
        ForcesUpdate()
      }
    })

    watch(containerWidth, async () => {
      if (props.clone || props.animateOnOverflowOnly) {
        ForcesUpdate()
      }
    })

    const animationState = computed(() => {

      if (props.vertical && verticalAnimationPause.value) {
        return 'paused'
      }

      if (props.animateOnOverflowOnly && animateOnOverflowPause.value) {
        return 'paused'
      }

      return 'running'
    })

    const getCurrentStyle: any = computed(() => {
      const cssVariables = {
        '--duration': `${props.duration}s`,
        '--delay': `${props.delay}s`,
        '--direction': `${props.direction}`,
        '--pauseAnimation': `${animationState.value}`,
        '--loops': `${props.loop === 0 ? 'infinite' : props.loop}`,
        '--gradient-length': `${gradientLength.value}`,
        '--min-width': `${minWidth.value}`,
        '--min-height': `${minHeight.value}`,
      }

      const animationStyles = {
        '--orientation': 'scrollX',
        orientation: 'horizontal',
      }

      if (props.vertical) {
        animationStyles['--orientation'] = 'scrollY'
      }

      const styles = {
        ...cssVariables,
        ...animationStyles,
      }

      return styles
    })

    const setupMarquee = async () => {
      if (props.vertical) {
        minHeight.value = '100%'
        minWidth.value = 'auto'

        if (props.animateOnOverflowOnly) {
          console.warn(
            'The `animateOnOverflowOnly` prop is not supported for vertical marquees.',
          )
        }
      } else {
        minHeight.value = 'auto'

        if (props.animateOnOverflowOnly) {
          minWidth.value = 'auto'
        } else {
          minWidth.value = '100%'
        }
      }

      if (props.clone || props.animateOnOverflowOnly) {
        await checkForClone()
        ForcesUpdate()
        ready.value = true
      } else {
        ready.value = true
      }
    }

    onMounted(async () => {
      setupMarquee()

      loopInterval.value = setInterval(() => {
        loopCounter.value++

        if (props.loop !== 0 && loopCounter.value === props.loop) {
          emit('onComplete')
          clearInterval(loopInterval.value)
        }

        emit('onLoopComplete')

        // Converting the duration into milliseconds here
      }, props.duration * 1000)
    })

    onBeforeUnmount(() => {
      clearInterval(loopInterval.value)
    })

    return {
      ready,
      contentWidth,
      containerWidth,
      contentHeight,
      containerHeight,
      loopCounter,
      loopInterval,
      mouseOverMarquee,
      mouseDownMarquee,
      minWidth,
      minHeight,
      animateOnOverflowPause,
      marqueeContent,
      marqueeOverlayContainer,
      componentKey,
      cloneAmount,
      ForcesUpdate,
      checkForClone,
      setupMarquee,
      getCurrentStyle,
    }
  },
})
</script>

<style>
.vue3-marquee {
  display: flex !important;
  position: relative;
}

.vue3-marquee.horizontal {
  overflow-x: hidden !important;
  flex-direction: row !important;
  width: 100%;
  height: max-content;
}

.vue3-marquee.vertical {
  overflow-y: hidden !important;
  flex-direction: column !important;
  height: 100%;
  width: max-content;
}

.vue3-marquee > .marquee {
  flex: 0 0 auto;
  min-width: var(--min-width);
  min-height: var(--min-height);
  z-index: 1;

  animation: var(--orientation) var(--duration) linear var(--delay) var(--loops);
  animation-play-state: var(--pauseAnimation);
  animation-direction: var(--direction);
}

.vue3-marquee.horizontal > .marquee {
  display: flex;
  flex-direction: row;
  align-items: center;
}

.vue3-marquee.vertical > .marquee {
  display: flex;
  flex-direction: column;
  align-items: center;
}

@keyframes scrollX {
  0% {
    transform: translateX(0%);
  }
  100% {
    transform: translateX(-100%);
  }
}

@keyframes scrollY {
  0% {
    transform: translateY(0%);
  }
  100% {
    transform: translateY(-100%);
  }
}

.vue3-marquee > .overlay {
  position: absolute;
  width: 100%;
  height: 100%;
}

.vue3-marquee > .transparent-overlay {
  position: absolute;
  width: 100%;
  height: 100%;
}

</style>